
#导入串口模块
from machine import UART
import re
import time
from binascii import hexlify
from GPIO import *
from loginfo import *
class Em485():
    _pinrx=prx
    _pintx=ptx
    _parity=0
    
    def __init__(self):
        self.log= Logs('aEm485',debugflag)
        self.log.append('aEm485初始化--'+str(debugflag))
        pass
    
   # def reverse(self,inbytes):
   #           return  bytes([dd for dd in reversed(inbytes)])
    #将字符串格式化16进制字节并做加hexadd处理
    def fromhex(self,hexstr):
        return bytes([(int(hexstr[i:i+2], 16))%256 for i in range(0, len(hexstr), 2)])
    # 数据加33处理
    def ADD0x33(self,data):
        return bytes([(data[i]+0x33)%256 for i in range(0, len(data), 1)])
    
    def CRC(self,checkList):
        return sum([check_l for check_l in checkList])%256
    
    def data_list_process(self, addressField, controlCode, dataFieldLength, dateFieldStr=None):
       # print(f'{addressField} {controlCode} {dataFieldLength} {dateFieldStr}')
        checkList = bytes([0x68])        
        dateField = bytes(reversed(self.fromhex(dateFieldStr)))      
        dateField = self.ADD0x33(dateField)
        addr=bytes(reversed(self.fromhex(addressField)))
        checkList =checkList + addr
        checkList =checkList+ bytes([0x68]) + self.fromhex(controlCode)+self.fromhex(dataFieldLength)
        checkList =checkList + dateField #68启始符
        checkCode = self.CRC(checkList)
        return bytes([0xFE,0xFE,0xFE,0xFE])  +checkList + bytes([checkCode, 0x16])   # 16结束符

    def read(self, com='1', Address='AAAAAAAAAAAA', control_code='11', data_length='04', data='0000FF00', baudrate=2400):
        
        rs = None
        sc =UART(2,baudrate=baudrate,parity=self._parity,tx=self._pinrx,rx=self._pintx)
        data_list = self.data_list_process(Address, control_code, data_length, data)       
        hex_string = hexlify(data_list).decode('utf-8')
        #print(f'串口发送: {hex_string}')
        sd = data_list
        sc.write(sd)
        time.sleep(0.05)
        start = time.ticks_ms()
       # print(f'开始:{start:.3f}')
        #判断有无收到信息
        rec_msg=None
        rec_len=0
        while True:
            rec_len = sc.any()
            time.sleep(0.05) 
            if rec_len!=0:
                self.log.append(f'读表DI:{data},通讯返回字节{rec_len}')
                break
            if(time.ticks_ms()-start)>=3000:
                self.log.append(f'读表DI:{data},通讯超时')
                break
        rec_msg=sc.read(rec_len)  #接收rec_len个字符
        
        #接收到数据不等于空，进行数据处理
        if rec_msg !=None:
            hex_string = hexlify(rec_msg).decode('utf-8')
            self.log.append('串口接收',hex_string)
            rs = self.to_data(rec_msg)
        
        self.log.append(f'耗时:{time.ticks_ms()-start}毫秒')   
        self.log.append(f'读取电表数据返回:{rs}')        
        return rs

    def __parse(self, rs):
        msg = ''
        if rs is not None and len(rs) != 0:           
            msg = hexlify(rs).decode('utf-8')
        #print(f'解析[{msg}]')

        if len(msg) == 0:
            return None

        data_len = len(rs)
       
        start_idx = rs.find(bytes([0x68]))
       
        ori_start_idx = start_idx
        if start_idx < 0:
            return None
        
        if start_idx + 12 > data_len:
            return None

        start1 = rs[start_idx]
        #print(f"帧起始符[{'%02X' % start1}]")
        if(start1!=0x68):
            return None
        start_idx += 1
        Address = rs[start_idx : start_idx + 6]
        Address = reversed(Address)
        Address=''.join('%02X' % x for x in Address)
        #print(f"  地址域[{Address}]")

        start_idx += 6
        start2 = rs[start_idx]
        #print(f"帧起始符[{'%02X' % start2}]")
        if(start2!=0x68):
            return None
        start_idx += 1
        ctl = rs[start_idx]
        #print(f"  控制符[{'%02X' % ctl}]")
        if '%02X' % ctl != '91':
            print(f"  控制符[{'%02X' % ctl}]")
            return ["EEFFFFFFFFFFFFEE",[],0]

        start_idx += 1
        dlen = rs[start_idx]
        #print(f"数据长度[{'%02X' % dlen}]")

               
        data = []
        start_idx += 1
        if dlen != 0:
            if start_idx + dlen > data_len:
                return None
            data = rs[start_idx: start_idx+dlen]
       # else:# 确认数据有异常
       #     return None

        #print(f"  数据阈[{' '.join('%02X' % x for x in data)}]")

        start_idx += dlen
        if start_idx + 2 > data_len:
            return None
        
        cc = rs[start_idx]
        #print(f"  校验码[{'%02X' % cc}]")

        fs = rs[start_idx+1]
        #print(f"  结束符[{'%02X' % fs}]")
        if(fs!=0x16):
            return None

        ck_list = rs[ori_start_idx: start_idx]
     
        checkCode = hex(sum(ck_list)).replace('0x', '').upper()
        checkCode = checkCode[-2:]
        #print(f"校验数据[{' '.join('%02X' % x for x in ck_list)}]")
        #print(f"  校验码[{checkCode}]")
        if checkCode != '%02X' % cc:
            self.log.append('校验码错误')
            self.log.append(f"  校验码[{checkCode}]")
            return None
        
        return [Address,data,data_len]
    

    #处理一个时间类型数据YYMMDDHHmmSS
    def to_datetime(self,d):
        d_t = d
        d_t.reverse()        
        try:        
            year =int(''.join(d_t[0]))
            month = int(''.join(d_t[1]))
            day = int(''.join(d_t[2]))
            hour = int(''.join(d_t[3]))
            minute = int(''.join(d_t[4]))
            second = int(''.join(d_t[5]))
           
            time_str = str(year)+'-'+str(month)+'-'+str(day)+' '+str(hour)+':'+str(minute)+':'+str(second)                 
        except:
            return None
        return time_str
    
    #处理一个时间类型数据YYMMDDWWHHmmSS
    def to_datetimeweek(self,d):
        d_t = d
        d_t.reverse()        
        try:        
            year =int(''.join(d_t[0]))
            month = int(''.join(d_t[1]))
            day = int(''.join(d_t[2]))
            week = int(''.join(d_t[3]))
            hour = int(''.join(d_t[4]))
            minute = int(''.join(d_t[5]))
            second = int(''.join(d_t[6]))
            
            time_str = str(year)+'-'+str(month)+'-'+str(day)+' '+str(hour)+':'+str(minute)+':'+str(second)                 
        except:
            return None
        return time_str
    
    def count_decimal_places(value):
        if isinstance(value, float):  # 确保是浮点数类型
            decimal_part = str(value).split('.')
            return len(decimal_part)
        else:
            return 0  # 非浮点数返回0位小数
        
    #处理一个数值数据 小数位数 传入参数0,1,2,3  0 = int 
    def to_item_one(self,d,decimal_point=0):
        if d[0] == 'FF':
            return None
        d_t = d
        d_t.reverse()

     
        if(decimal_point==0):
            return int(''.join(d_t))
        else:
            r=float(''.join(d_t))/n
            if(decimal_point!=count_decimal_places(r)):
                 self.log.appenderror('解析小数位数异常'+d,e)   
            return r
            #return round(float(''.join(d_t))/n,decimal_point)
    
    #处理电能量数据(总尖峰平谷）
    def to_energy(self,d,prefix=""):
        dd = []
        for i in range(0, 20, 4):
            dd.append(self.to_item_one(d[i:i+4],2))                  
        data = {            
            prefix+"Total": dd[0],
            prefix+"peak": dd[1],
            prefix+"tip": dd[2],
            prefix+"flat": dd[3],
            prefix+"valley": dd[4]
            }
        return data
    #处理电压数据ABC 相角数据也参考此数据
    def to_voltage(self,d):
       
        dd = []
        for i in range(0, 6, 2):
            dd.append(self.to_item_one(d[i:i+2],1))                  
        data = {            
            "UA": dd[0],
            "UB": dd[1],
            "UC": dd[2]        
            }
        return data
    
     #处理功率数据总、A、B、C 
    def to_Power(self,d,prefix=""):
        dd = []
        for i in range(0, 12, 3):
            dd.append(self.to_item_one(d[i:i+3],4))                  
        data = {
            prefix+"Total": dd[0],
            prefix+"A": dd[1],
            prefix+"B": dd[2],
            prefix+"C": dd[3]        
            }
        return data
    #相角
    def to_PA(self,d):
        dd = []
        for i in range(0, 6, 2):
            dd.append(self.to_item_one(d[i:i+2],1))                  
        data = {            
            "PAA": dd[0],
            "PAB": dd[1],
            "PAC": dd[2]        
            }
        return data
    
      #处理电流数据ABC
    def to_current(self,d):
        dd = []
        for i in range(0, 9, 3):
            dd.append(self.to_item_one(d[i:i+3],3))                  
        data = {            
            "IA": dd[0],
            "IB": dd[1],
            "IC": dd[2]        
            }
        return data
    
   #零线电流
    def to_ZeroLC(self,d):
        dd = []
        dd.append(self.to_item_one(d[0:3],3))                  
        data = {            
            "ZeroLC": dd[0]
            }
        return data
  #电表时钟
    def to_MeterTime(self,d,prefix=""):
        dd = []
        #时间数据     
        dd.append(self.to_datetimeweek(d[0:7]))
      
        data = {
            "MeterTime"+prefix: dd[0]              
            }
        return data
    #处理开盖记录
    def to_opencoverrecord(self,d,prefix=""):
        dd = []
        #时间数据
        for i in range(0, 12, 6):
            dd.append(self.to_datetime(d[i:i+6]))
        #电能量数据
        for i in range(12, 48, 4):
            dd.append(self.to_item_one(d[i:i+4],2))
        data = {
            "SD"+prefix: dd[0],
            "ED"+prefix: dd[1],        
            "BPAE"+prefix: dd[2],
            "BRAE"+prefix: dd[3],
            "APAE"+prefix: dd[8],
            "ARAE"+prefix: dd[9],           
            }
        return data
    #电表状态字
    def to_statusword(self, d):
        dd = []
        statusword=[]
        effective=[[0,2,3,4,5,8,9,12,13,14,15],[],[],[0,1,2,3,4,5,6,7,8,12,13],[0,1,2,3,4,5,6,7,8,12,13],[0,1,2,3,4,5,6,7,8,12,13],[0,1,2,3,4,5,6,7,8,9,10,11,12]]
        datalen = len(d)
        if(datalen>14):
            datalen =14
        
        for i in range(0, datalen, 2):
            onestatusword=int(d[i])# 一个状态字
            j =int(i/2)
            #dd[j] =0xffff#测试代码 返回状态全部是异常 正式环境需注释掉
          
            for effectivebit in effective[j]:
                if((0x01 << effectivebit & onestatusword)>0):
                    statusword.append(effectivebit+i*8)
                    
        data ={"statusword":str(statusword)}
      
        return data
                
                
            
    #数据解析主模块
    def to_data(self, data):
        try:
            rs = self.__parse(data)
        except  Exception as e:
            self.log.append('读表框架解析发生异常__parse(data):',e)
            rs=None

        if rs is None:
            return None
        #print(rs)
        #d = ['%02X' % (((x if x != 0x32 else 0x33) - 0x33)%256) for x in rs[1]]
        #d = ['%02X' % ((x- 0x33)%256) for x in rs[1]]
        d = ['%02X' % ((x - 0x33)&0xFF) for x in rs[1]]
        #print('解析结果:')
        #print(d)
        df = d[0:4]
        df.reverse()
        self.log.append(f"数据标识[{' '.join(df)}]")       
        #DI定义
        DI=''.join(df)
        #电能类数据
        energy_data={
                 '0001FF00':('正向有功','PA'),
                 '0002FF00':('反向有功','RA'),
                 '0001FF01':('上月正向有功','LPA'),
                 '0002FF01':('上月反向有功','LRA')   
        }
        #开盖次数
        Opencovertimes='03300D00'
        
        #开盖记录
        opencoverrecord={
            '03300D01':('（上1次）开表盖记录','1'),
            '03300D02':('（上2次）开表盖记录','2')}
        #电压数据
        voltage_data='0201FF00'
        #电流数据
        current_data='0202FF00'
        #相角数据 使用电压数据解析
        phase_angle ='0207FF00'
        
        #有功功率数据
        #power_data='0203FF00'      
        power_data={
                 '0203FF00':('有功功率','P'),
                 '0204FF00':('无功功率','Q')                 
        }
        status= {}
        #电表状态字
        statuswordDI='040005FF'
        #零线电流
        ZeroLCDI ='02800001'
        #电表时钟
        MeterTimeDI='0400010C'
        rdata={"Address":rs[0]}
        datalen = rs[2]
        
        if(rs[0]=='EEFFFFFFFFFFFFEE'):
            return rdata
        try:
            if energy_data.get(DI)!=None:#有/无功电能量
                #print(energy_data.get(DI)[1])
                rdata.update(self.to_energy(d[4:datalen],energy_data.get(DI)[1]))
                rdata.update({'data_type':DI})
            elif Opencovertimes == DI:#开盖次数
                df=d[4:datalen]
                df.reverse()
                rdata.update({'Opencovertimes': int(''.join(df))})
                rdata.update({'data_type':DI})            
            elif opencoverrecord.get(DI)!=None:#开盖几率
                rdata.update(self.to_opencoverrecord(d[4:datalen],opencoverrecord.get(DI)[1]))
                rdata.update({'data_type':DI})
                
            elif voltage_data == DI:#电压
                rdata.update(self.to_voltage(d[4:datalen]))
                rdata.update({'data_type':DI})
                
            elif current_data == DI:#电流
                rdata.update(self.to_current(d[4:datalen]))
                rdata.update({'data_type':DI})
                
            elif phase_angle == DI:#相角
                rdata.update(self.to_PA(d[4:datalen]))
                rdata.update({'data_type':DI})
                
            elif power_data.get(DI)!=None:#有/无工功率
                rdata.update(self.to_Power(d[4:datalen],power_data.get(DI)[1]))
                rdata.update({'data_type':DI})
                
            elif statuswordDI == DI:#状态字
                rdata.update(self.to_statusword(d[4:datalen]))
                rdata.update({'data_type':DI})
                
            elif ZeroLCDI == DI:#零线电流
                rdata.update(self.to_ZeroLC(d[4:datalen]))
                rdata.update({'data_type':DI})
                
            elif MeterTimeDI == DI:#电表时钟
                rdata.update(self.to_MeterTime(d[4:datalen]))
                rdata.update({'data_type':DI})
            else:
                rdata=None
        except  Exception as e:
            self.log.append('读表 rdata.update 数据域解析发生异常 :',e)
            rs=None        
           
            #self.log.append(f"数据标识[{' '.join(d)}]")       
        rdata.update({'original':rs})
        return rdata
    